// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/loader/mojo_async_resource_handler.h"

#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/containers/hash_tables.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "content/browser/loader/downloaded_temp_file_impl.h"
#include "content/browser/loader/netlog_observer.h"
#include "content/browser/loader/resource_controller.h"
#include "content/browser/loader/resource_dispatcher_host_impl.h"
#include "content/browser/loader/resource_request_info_impl.h"
#include "content/common/resource_request_completion_status.h"
#include "content/public/browser/global_request_id.h"
#include "content/public/browser/resource_dispatcher_host_delegate.h"
#include "content/public/common/resource_response.h"
#include "mojo/public/c/system/data_pipe.h"
#include "mojo/public/cpp/bindings/message.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
#include "net/base/mime_sniffer.h"
#include "net/url_request/redirect_info.h"

namespace content {
namespace {

    int g_allocation_size = MojoAsyncResourceHandler::kDefaultAllocationSize;

    // MimeTypeResourceHandler *implicitly* requires that the buffer size
    // returned from OnWillRead should be larger than certain size.
    // TODO(yhirano): Fix MimeTypeResourceHandler.
    constexpr size_t kMinAllocationSize = 2 * net::kMaxBytesToSniff;

    constexpr size_t kMaxChunkSize = 32 * 1024;

    void GetNumericArg(const std::string& name, int* result)
    {
        const std::string& value = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(name);
        if (!value.empty())
            base::StringToInt(value, result);
    }

    void InitializeResourceBufferConstants()
    {
        static bool did_init = false;
        if (did_init)
            return;
        did_init = true;

        GetNumericArg("resource-buffer-size", &g_allocation_size);
    }

} // namespace

// This class is for sharing the ownership of a ScopedDataPipeProducerHandle
// between WriterIOBuffer and MojoAsyncResourceHandler.
class MojoAsyncResourceHandler::SharedWriter final
    : public base::RefCountedThreadSafe<SharedWriter> {
public:
    explicit SharedWriter(mojo::ScopedDataPipeProducerHandle writer)
        : writer_(std::move(writer))
    {
    }
    mojo::DataPipeProducerHandle writer() { return writer_.get(); }

private:
    friend class base::RefCountedThreadSafe<SharedWriter>;
    ~SharedWriter() { }

    const mojo::ScopedDataPipeProducerHandle writer_;

    DISALLOW_COPY_AND_ASSIGN(SharedWriter);
};

// This class is a IOBuffer subclass for data gotten from a
// ScopedDataPipeProducerHandle.
class MojoAsyncResourceHandler::WriterIOBuffer final
    : public net::IOBufferWithSize {
public:
    // |data| and |size| should be gotten from |writer| via BeginWriteDataRaw.
    // They will be accesible via IOBuffer methods. As |writer| is stored in this
    // instance, |data| will be kept valid as long as the following conditions
    // hold:
    //  1. |data| is not invalidated via EndWriteDataRaw.
    //  2. |this| instance is alive.
    WriterIOBuffer(scoped_refptr<SharedWriter> writer, void* data, size_t size)
        : net::IOBufferWithSize(static_cast<char*>(data), size)
        , writer_(std::move(writer))
    {
    }

private:
    ~WriterIOBuffer() override
    {
        // Avoid deleting |data_| in the IOBuffer destructor.
        data_ = nullptr;
    }

    // This member is for keeping the writer alive.
    scoped_refptr<SharedWriter> writer_;

    DISALLOW_COPY_AND_ASSIGN(WriterIOBuffer);
};

MojoAsyncResourceHandler::MojoAsyncResourceHandler(
    net::URLRequest* request,
    ResourceDispatcherHostImpl* rdh,
    mojom::URLLoaderAssociatedRequest mojo_request,
    mojom::URLLoaderClientAssociatedPtr url_loader_client)
    : ResourceHandler(request)
    , rdh_(rdh)
    , binding_(this, std::move(mojo_request))
    , url_loader_client_(std::move(url_loader_client))
    , weak_factory_(this)
{
    DCHECK(url_loader_client_);
    InitializeResourceBufferConstants();
    // This unretained pointer is safe, because |binding_| is owned by |this| and
    // the callback will never be called after |this| is destroyed.
    binding_.set_connection_error_handler(
        base::Bind(&MojoAsyncResourceHandler::Cancel, base::Unretained(this)));

    GetRequestInfo()->set_on_transfer(base::Bind(
        &MojoAsyncResourceHandler::OnTransfer, weak_factory_.GetWeakPtr()));
}

MojoAsyncResourceHandler::~MojoAsyncResourceHandler()
{
    if (has_checked_for_sufficient_resources_)
        rdh_->FinishedWithResourcesForRequest(request());
}

bool MojoAsyncResourceHandler::OnRequestRedirected(
    const net::RedirectInfo& redirect_info,
    ResourceResponse* response,
    bool* defer)
{
    // Unlike OnResponseStarted, OnRequestRedirected will NOT be preceded by
    // OnWillRead.
    DCHECK(!shared_writer_);

    *defer = true;
    request()->LogBlockedBy("MojoAsyncResourceHandler");
    did_defer_on_redirect_ = true;

    NetLogObserver::PopulateResponseInfo(request(), response);
    response->head.encoded_data_length = request()->GetTotalReceivedBytes();
    response->head.request_start = request()->creation_time();
    response->head.response_start = base::TimeTicks::Now();
    // TODO(davidben): Is it necessary to pass the new first party URL for
    // cookies? The only case where it can change is top-level navigation requests
    // and hopefully those will eventually all be owned by the browser. It's
    // possible this is still needed while renderer-owned ones exist.
    url_loader_client_->OnReceiveRedirect(redirect_info, response->head);
    return true;
}

bool MojoAsyncResourceHandler::OnResponseStarted(ResourceResponse* response,
    bool* defer)
{
    const ResourceRequestInfoImpl* info = GetRequestInfo();

    if (rdh_->delegate()) {
        rdh_->delegate()->OnResponseStarted(request(), info->GetContext(),
            response);
    }

    NetLogObserver::PopulateResponseInfo(request(), response);
    response->head.encoded_data_length = request()->raw_header_size();
    reported_total_received_bytes_ = response->head.encoded_data_length;

    response->head.request_start = request()->creation_time();
    response->head.response_start = base::TimeTicks::Now();
    sent_received_response_message_ = true;

    mojom::DownloadedTempFilePtr downloaded_file_ptr;
    if (!response->head.download_file_path.empty()) {
        downloaded_file_ptr = DownloadedTempFileImpl::Create(info->GetChildID(),
            info->GetRequestID());
        rdh_->RegisterDownloadedTempFile(info->GetChildID(), info->GetRequestID(),
            response->head.download_file_path);
    }

    url_loader_client_->OnReceiveResponse(response->head,
        std::move(downloaded_file_ptr));

    net::IOBufferWithSize* metadata = GetResponseMetadata(request());
    if (metadata) {
        const uint8_t* data = reinterpret_cast<const uint8_t*>(metadata->data());

        url_loader_client_->OnReceiveCachedMetadata(
            std::vector<uint8_t>(data, data + metadata->size()));
    }
    return true;
}

bool MojoAsyncResourceHandler::OnWillStart(const GURL& url, bool* defer)
{
    return true;
}

bool MojoAsyncResourceHandler::OnWillRead(scoped_refptr<net::IOBuffer>* buf,
    int* buf_size,
    int min_size)
{
    DCHECK_EQ(-1, min_size);

    if (!CheckForSufficientResource())
        return false;

    if (!shared_writer_) {
        MojoCreateDataPipeOptions options;
        options.struct_size = sizeof(MojoCreateDataPipeOptions);
        options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
        options.element_num_bytes = 1;
        options.capacity_num_bytes = g_allocation_size;
        mojo::DataPipe data_pipe(options);

        DCHECK(data_pipe.producer_handle.is_valid());
        DCHECK(data_pipe.consumer_handle.is_valid());

        response_body_consumer_handle_ = std::move(data_pipe.consumer_handle);
        shared_writer_ = new SharedWriter(std::move(data_pipe.producer_handle));
        handle_watcher_.Start(shared_writer_->writer(), MOJO_HANDLE_SIGNAL_WRITABLE,
            base::Bind(&MojoAsyncResourceHandler::OnWritable,
                base::Unretained(this)));

        bool defer = false;
        scoped_refptr<net::IOBufferWithSize> buffer;
        if (!AllocateWriterIOBuffer(&buffer, &defer))
            return false;
        if (!defer) {
            if (static_cast<size_t>(buffer->size()) >= kMinAllocationSize) {
                *buf = buffer_ = buffer;
                *buf_size = buffer_->size();
                return true;
            }

            // The allocated buffer is too small.
            if (EndWrite(0) != MOJO_RESULT_OK)
                return false;
        }
        DCHECK(!is_using_io_buffer_not_from_writer_);
        is_using_io_buffer_not_from_writer_ = true;
        buffer_ = new net::IOBufferWithSize(kMinAllocationSize);
    }

    DCHECK_EQ(0u, buffer_offset_);
    *buf = buffer_;
    *buf_size = buffer_->size();
    return true;
}

bool MojoAsyncResourceHandler::OnReadCompleted(int bytes_read, bool* defer)
{
    DCHECK_GE(bytes_read, 0);
    DCHECK(buffer_);

    if (!bytes_read)
        return true;

    const ResourceRequestInfoImpl* info = GetRequestInfo();
    if (info->ShouldReportRawHeaders()) {
        auto transfer_size_diff = CalculateRecentlyReceivedBytes();
        if (transfer_size_diff > 0)
            url_loader_client_->OnTransferSizeUpdated(transfer_size_diff);
    }

    if (response_body_consumer_handle_.is_valid()) {
        // Send the data pipe on the first OnReadCompleted call.
        url_loader_client_->OnStartLoadingResponseBody(
            std::move(response_body_consumer_handle_));
        response_body_consumer_handle_.reset();
    }

    if (is_using_io_buffer_not_from_writer_) {
        // Couldn't allocate a buffer on the data pipe in OnWillRead.
        DCHECK_EQ(0u, buffer_bytes_read_);
        buffer_bytes_read_ = bytes_read;
        if (!CopyReadDataToDataPipe(defer))
            return false;
        if (*defer) {
            request()->LogBlockedBy("MojoAsyncResourceHandler");
            did_defer_on_writing_ = true;
        }
        return true;
    }

    if (EndWrite(bytes_read) != MOJO_RESULT_OK)
        return false;
    // Allocate a buffer for the next OnWillRead call here, because OnWillRead
    // doesn't have |defer| parameter.
    if (!AllocateWriterIOBuffer(&buffer_, defer))
        return false;
    if (*defer) {
        request()->LogBlockedBy("MojoAsyncResourceHandler");
        did_defer_on_writing_ = true;
    }
    return true;
}

void MojoAsyncResourceHandler::OnDataDownloaded(int bytes_downloaded)
{
    url_loader_client_->OnDataDownloaded(bytes_downloaded,
        CalculateRecentlyReceivedBytes());
}

void MojoAsyncResourceHandler::FollowRedirect()
{
    if (!request()->status().is_success()) {
        DVLOG(1) << "FollowRedirect for invalid request";
        return;
    }
    if (!did_defer_on_redirect_) {
        DVLOG(1) << "Malformed FollowRedirect request";
        ReportBadMessage("Malformed FollowRedirect request");
        return;
    }

    DCHECK(!did_defer_on_writing_);
    did_defer_on_redirect_ = false;
    request()->LogUnblocked();
    controller()->Resume();
}

void MojoAsyncResourceHandler::OnWritableForTesting()
{
    OnWritable(MOJO_RESULT_OK);
}

void MojoAsyncResourceHandler::SetAllocationSizeForTesting(size_t size)
{
    g_allocation_size = size;
}

MojoResult MojoAsyncResourceHandler::BeginWrite(void** data,
    uint32_t* available)
{
    MojoResult result = mojo::BeginWriteDataRaw(
        shared_writer_->writer(), data, available, MOJO_WRITE_DATA_FLAG_NONE);
    if (result == MOJO_RESULT_OK)
        *available = std::min(*available, static_cast<uint32_t>(kMaxChunkSize));
    return result;
}

MojoResult MojoAsyncResourceHandler::EndWrite(uint32_t written)
{
    return mojo::EndWriteDataRaw(shared_writer_->writer(), written);
}

net::IOBufferWithSize* MojoAsyncResourceHandler::GetResponseMetadata(
    net::URLRequest* request)
{
    return request->response_info().metadata.get();
}

void MojoAsyncResourceHandler::OnResponseCompleted(
    const net::URLRequestStatus& status,
    bool* defer)
{
    shared_writer_ = nullptr;
    buffer_ = nullptr;
    handle_watcher_.Cancel();

    const ResourceRequestInfoImpl* info = GetRequestInfo();

    // TODO(gavinp): Remove this CHECK when we figure out the cause of
    // http://crbug.com/124680 . This check mirrors closely check in
    // WebURLLoaderImpl::OnCompletedRequest that routes this message to a WebCore
    // ResourceHandleInternal which asserts on its state and crashes. By crashing
    // when the message is sent, we should get better crash reports.
    CHECK(status.status() != net::URLRequestStatus::SUCCESS || sent_received_response_message_);

    int error_code = status.error();
    bool was_ignored_by_handler = info->WasIgnoredByHandler();

    DCHECK_NE(status.status(), net::URLRequestStatus::IO_PENDING);
    // If this check fails, then we're in an inconsistent state because all
    // requests ignored by the handler should be canceled (which should result in
    // the ERR_ABORTED error code).
    DCHECK(!was_ignored_by_handler || error_code == net::ERR_ABORTED);

    ResourceRequestCompletionStatus request_complete_data;
    request_complete_data.error_code = error_code;
    request_complete_data.was_ignored_by_handler = was_ignored_by_handler;
    request_complete_data.exists_in_cache = request()->response_info().was_cached;
    request_complete_data.completion_time = base::TimeTicks::Now();
    request_complete_data.encoded_data_length = request()->GetTotalReceivedBytes();
    request_complete_data.encoded_body_length = request()->GetRawBodyBytes();

    url_loader_client_->OnComplete(request_complete_data);
}

bool MojoAsyncResourceHandler::CopyReadDataToDataPipe(bool* defer)
{
    while (true) {
        scoped_refptr<net::IOBufferWithSize> dest;
        if (!AllocateWriterIOBuffer(&dest, defer))
            return false;
        if (*defer)
            return true;
        if (buffer_bytes_read_ == 0) {
            // All bytes are copied. Save the buffer for the next OnWillRead call.
            buffer_ = std::move(dest);
            return true;
        }

        size_t copied_size = std::min(buffer_bytes_read_, static_cast<size_t>(dest->size()));
        memcpy(dest->data(), buffer_->data() + buffer_offset_, copied_size);
        buffer_offset_ += copied_size;
        buffer_bytes_read_ -= copied_size;
        if (EndWrite(copied_size) != MOJO_RESULT_OK)
            return false;

        if (buffer_bytes_read_ == 0) {
            // All bytes are copied.
            buffer_offset_ = 0;
            is_using_io_buffer_not_from_writer_ = false;
        }
    }
}

bool MojoAsyncResourceHandler::AllocateWriterIOBuffer(
    scoped_refptr<net::IOBufferWithSize>* buf,
    bool* defer)
{
    void* data = nullptr;
    uint32_t available = 0;
    MojoResult result = BeginWrite(&data, &available);
    if (result == MOJO_RESULT_SHOULD_WAIT) {
        *defer = true;
        return true;
    }
    if (result != MOJO_RESULT_OK)
        return false;
    *buf = new WriterIOBuffer(shared_writer_, data, available);
    return true;
}

bool MojoAsyncResourceHandler::CheckForSufficientResource()
{
    if (has_checked_for_sufficient_resources_)
        return true;
    has_checked_for_sufficient_resources_ = true;

    if (rdh_->HasSufficientResourcesForRequest(request()))
        return true;

    controller()->CancelWithError(net::ERR_INSUFFICIENT_RESOURCES);
    return false;
}

void MojoAsyncResourceHandler::OnWritable(MojoResult result)
{
    if (!did_defer_on_writing_)
        return;
    DCHECK(!did_defer_on_redirect_);
    did_defer_on_writing_ = false;

    if (is_using_io_buffer_not_from_writer_) {
        // |buffer_| is set to a net::IOBufferWithSize. Write the buffer contents
        // to the data pipe.
        DCHECK_GT(buffer_bytes_read_, 0u);
        if (!CopyReadDataToDataPipe(&did_defer_on_writing_)) {
            controller()->CancelWithError(net::ERR_FAILED);
            return;
        }
    } else {
        // Allocate a buffer for the next OnWillRead call here.
        if (!AllocateWriterIOBuffer(&buffer_, &did_defer_on_writing_)) {
            controller()->CancelWithError(net::ERR_FAILED);
            return;
        }
    }

    if (did_defer_on_writing_) {
        // Continue waiting.
        return;
    }
    request()->LogUnblocked();
    controller()->Resume();
}

void MojoAsyncResourceHandler::Cancel()
{
    const ResourceRequestInfoImpl* info = GetRequestInfo();
    ResourceDispatcherHostImpl::Get()->CancelRequestFromRenderer(
        GlobalRequestID(info->GetChildID(), info->GetRequestID()));
}

int64_t MojoAsyncResourceHandler::CalculateRecentlyReceivedBytes()
{
    int64_t total_received_bytes = request()->GetTotalReceivedBytes();
    int64_t bytes_to_report = total_received_bytes - reported_total_received_bytes_;
    reported_total_received_bytes_ = total_received_bytes;
    DCHECK_LE(0, bytes_to_report);
    return bytes_to_report;
}

void MojoAsyncResourceHandler::ReportBadMessage(const std::string& error)
{
    mojo::ReportBadMessage(error);
}

void MojoAsyncResourceHandler::OnTransfer(
    mojom::URLLoaderAssociatedRequest mojo_request,
    mojom::URLLoaderClientAssociatedPtr url_loader_client)
{
    binding_.Unbind();
    binding_.Bind(std::move(mojo_request));
    binding_.set_connection_error_handler(
        base::Bind(&MojoAsyncResourceHandler::Cancel, base::Unretained(this)));
    url_loader_client_ = std::move(url_loader_client);
}

} // namespace content
